skills(pm-dispatch): a seated session reads whether origin/main touched its charter before its first write, and re-reads on a hit - #17136
Conversation
…ed its charter before its first write, and re-reads on a hit The round-open sequence of SKILL.md 〈入口与角色〉 gains one criterion: after the mutex clears, the seat fetches and reads the newest touch of the three charter files it loaded (SKILL.md, references/core-rules.md, its own lane file) on origin/main; a touch different from the one its previous round-open marker recorded means it re-reads those files before acting, and the marker now carries that touch and names the re-read. Paid in place: the seat-post block's restatement of the archive obligation leaves SKILL.md (it stays at references/seat-post-protocol.md item 8 and in the core-rules digest); core-rules.md absorbs the twin on its existing marker line at 119 bytes. A content reading, not a time window: on this mainline the queue's landed commit carries a committer date 19.6-34.1 min before merged_at (29 of 29 recent landings), so a window keyed to fire time is blind to a governed merge that predates a fire but lands after it — the inversion check-governed-merges.mjs already rules on. Claude-Session: https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ Co-authored-by: Claude <noreply@anthropic.com>
维护者速读 — PR #17136(#17007,skills 席终稿,2026-09-09T12:28Z)改了什么 — PM 派发技能 为什么改 — 分诊席 9 月 4 日就座时读入的章程,在 9 月 8 日一次受管合并改掉三句载体纪律之后依然是它行事的版本:每小时的 fire 只带任务指令,没有任何东西告诉已就座的会话「你读过的文本被改了」,于是同一上午写出十三次新文本明令禁止的预挂(#16995 的实测)。这是协议缺口:事后巡检能发现残留,事前没有读数。本 PR 让受管合并在下一次 fire 的开轮盘点里可见,且只在真的动过时才重读。卡面写的是按时间窗 风险与代价(含回滚) — 纯内部协议文本,无产品面。风险一:SKILL.md :43 说核心条款是「本文的子集」,删掉 :286 后 core-rules :79 那半句的正文出处从 SKILL.md 移到了它指向的 references 清单——本席判为指针够用、不构成丢规则,但这是可推翻的判断;若你要 SKILL.md 保留那句,替代付账是另找一行删减,不是压回一行(最短一行拼写 133 字节,超宽度 pin)。风险二:每次 fire 多一次零配额的 席位意见 — 接受。本席在分支头上复核:diff 三行(+3/−3),宽度 119/117/119 B,三份账本 812/151/91 不动;被删句两处原文都在; 你要做的 — 读 SKILL.md :89–:90 与 core-rules :20 三行,确认「读最新触碰提交并与上一标记比对」符合你对这条判据的本意;认可归档义务那句退回清单与摘要;CI 全绿后人工合并,或由 os-zhuang / hotlong 批准后本席入队(受管面,席位不翻 ready、不入队、不挂 auto-merge)。 Generated by Claude Code |
Fixes #17007
What this changes
.claude/skills/pm-dispatch/SKILL.md〈入口与角色〉, the round-open sequence: the marker line :89 becomes two lines :89–:90, and the seat-post block's restatement at :286 leaves the file — 812/812 lines, every touched line ≤120 bytes.references/core-rules.md:20 absorbs the twin in place — 151/151.references/seat-post-protocol.mdis untouched (91/91): measured, it carries no round-open marker line (grep -n 开轮 references/seat-post-protocol.md→ 0 hits; the only marker lines in the corpus are SKILL.md :89 and its digest at core-rules :20), so the marker's content spec lives on SKILL.md :90 alone and no third copy is created — dispatch assumption B held.Before (
0da638cd), widths byprintf '%s' LINE | wc -c:After (
7e75fb9f):Gloss: mutex clear ⇒ fetch, then read the newest touch (commit sha) of the three charter files the seat loaded — this file, core-rules, its own lane charter — on
origin/main. If it differs from the one the seat's previous round-open marker recorded, re-read those files before anything else; then leave the marker (session ID + fire time + that touch, noting a re-read) and run the round. The digest line names the step (after seating, verify the charter, then write the marker);有简报即坐席is the same clause with 收班 dropped — :19 names 收班简报 immediately above it. The sequence is the one the dispatch route suggested: mutex clear ⇒ charter-touch reading ⇒ re-read on a hit ⇒ marker naming the re-read. The marker's own fire timestamp stays; no seating time is needed (next section).The mechanism: a content reading, not a time window — dispatch assumption A measured, and partly falsified
scripts/pm/git-history.mjsdoes give a one-line, refusal-capable windowed reading:log --since=DATE --ref=origin/main --path=P(repeatable), exit 2 with empty stdout when the shallow floor sits inside the window, auto-deepen otherwise. It answers exactly the shape the card spelled (git log origin/main --since=SEATING -- PATHS).merged_atagainst the local%cIof eachmerge_commit_sha, the 29 most recently merged PRs: every landed commit is dated 19.6–34.1 min beforemerged_at(the queue builds the commit at enqueue;mainreceives it at landing). A window keyed to the marker's fire time — the sliding form the route suggested — is blind to a governed merge whose commit predates fire N but lands after it: absent at N (not onmainyet), absent at N+1 (dated below the window start). The card's literal fixed start--since=SEATINGhas no blind spot but re-hits on every fire after the first governed merge — the hourly read-through the card forbids.scripts/pm/check-governed-merges.mjsheader, 「The WINDOW is landing order, not committer dates」 — ruled 2026-08-27 on finding(check-governed-merges): the merge queue's build-to-land skew can put a governed landing BELOW a round's --since boundary, where it is silently absent from the audit list #12633, topological--since-refpreferred, a bare--sincebacked off by a declared 3,600 s budget. Today's skew (up to 2,046 s) exceeds the 874 s maximum that header re-measured on 2026-08-27 and sits inside its budget.git fetch origin main, thengit log -1 --format=%h origin/main -- PATHS, compared with the value on the seat's previous marker (today's reading for SKILL.md:3045b6fa). No window ⇒ no floor question: a new touch is always above any shallow floor, and a floor ABOVE the recorded touch reads the graft boundary's own sha — measured on this depth-1305 clone (floor1af82861, 2026-09-01):git log -1 --format=%h origin/main -- LICENSE, a path untouched in the visible window, prints1af82861, because a shallow boundary has no parent and git lists it as introducing every path — which mismatches the recorded sha and lands on the safe side: a spurious re-read, never a false clean. The one stale-clean path is an unfetched ref, which is why the line saysfetch 后. The helper is therefore not named: there is no windowed question left to ask. ⛔ No script added or changed.Sample of the 29: #17089 committed 07:52:11Z, merged 08:23:47Z (31.6 min); #17086 07:51:44Z → 08:23:47Z (32.0 min); #17091 07:55:36Z → 08:29:45Z (34.1 min); #17015 11:33:53Z → 11:53:27Z (19.6 min).
Paid in place — the one deletion and where the rule lives instead
归档自己派出的会话是不可移交的义务。references/seat-post-protocol.md:49 — handover checklist item 8,本座位派出的 dev 会话由离任 PM 自己逐个 archive_session,归档义务 ⛔ 不可移交— reached from SKILL.md :285 (交接按收尾清单逐步走完) and the pointer at :287; digested at core-rules.md :79. Provenance: the rules-only rewrite (791a0cbe, 2026-09-05), not a ruling landed today.Not touched: the anchoring-rule lines :239–:241, the batch-independence lines :438–:442, the queue-entry third case (:632–:633), the 说明书脱节 lines (:342 / :344 / :714).
core-rules.mdpays nothing: :20 had 4 bytes of headroom; dropping 收班 (−6 B) and replacing 先写 with 核章程再写 (+9 B) lands at 119 B — the mechanism stays in SKILL.md, as core-rules :3 prescribes. No re-wrap anywhere; no ceiling moves; ⛔ no issue number in the operational text (check:pm-skill-id-lintgreen).Reverse verification — the ratchet reads this file (on
7e75fb9f, fix committed first)wc -l812 → 813 →node scripts/pm/check-skill-line-ratchet.mjsexit 1, its own verdict✗ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 813 lines; the ratchet ceiling is 812.sha。xxcount 0 → 1) → exit 1, verdict✗ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md has 1 line(s) over the 120-byte budget: L89 (121B).git checkout HEAD -- ABSOLUTE_PATH(trap-guarded): marker count 0, anchor count 0, 812 lines,git diff HEAD0 lines,git status --porcelain0 lines, working-tree blob785d8b6603bdb405fd14314978a45a05c362971eequal toHEAD:PATH. The hash equality is the proof; the trap is only the crash path. Direction observed: red, as expected, on both legs.Gates — all on
7e75fb9f, exit captured by redirect before any pipeDerived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; 2 paths vs merge base0da638cd9; 16 commands — the same 16 the dispatch named) and reconciled with--ran:✓ dispatch-gates --ran: 16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED.pnpm check:pm-skill-ratchet✓ … SKILL.md is 812 lines (ceiling 812; headroom 0)·core-rules.md is 151 lines (ceiling 151; headroom 0)·seat-post-protocol.md is 91 lines (ceiling 91; headroom 0)pnpm check:pm-skill-id-lint✓ check-skill-id-lint: 26 file(s) clean (pattern /#[0-9]{3,}/g)pnpm check:skill-frame-sync✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 filespnpm check:pm-governed-merges✓ check-governed-merges --self-test: 274 assertions …pnpm check:pm-governed-prose✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces … and claim no otherspnpm check:nul-bytescheck-nul-bytes: OK (scanned 8057 text file(s) … no raw ASCII control bytes)pnpm check:doc-authoring✓ doc authoring guard: 46 published skill files clean·… 15172 customer-facing string(s) … clean·… sibling-package prose ids hold the baselinenode scripts/check-closing-keyword-parity.mjs(+--self-test)OK (3 parsers agree on all 9 keywords …)·✓ … 24 assertions, 5 mutations … driven to rednode scripts/check-comment-mask-corpus.mjs✓ comment-mask corpus sweep: 6427 files, 0 disagree, 0 unparseablenode scripts/pm/check-governed-queue-guard.mjs --self-test✓ check-governed-queue-guard self-test: 144 cases passpnpm check:agent-test-spelling·check:driver-memory-census·check:refd-timer-probe·check:watch-hint-literal✓ check-agent-test-spelling: 0 violations·check-driver-memory-census: OK·OK check-refd-timer-probe: 6422 source file(s) swept·✓ check-watch-hint-literal: 67 declaration(s) …pnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2under the verify lock (VERDICT command-exit 0 · held the lock 219s · waited 1s, 4/4 tasks built) — verdicts✓ check:doc-formula-expressions (spec TSDoc): 9 @example(s) judged clean across 1315 packages/spec/src files·✓ … (field-level *When): 14 predicate(s) … judged cleanGoverned predicate, copied from
node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md .claude/skills/pm-dispatch/references/core-rules.md(exit 3):⇒ draft, human merge. No seat flips it ready, queues it, arms auto-merge, or approves it.
skip-changeset:.claude/**ships in no package'sfiles[](fast lane, not published).Clause-②: noper the claim.Deviations from the dispatch, stated
git log --since; the line reads the newest touch on the tip instead (measured above). Executed under the ruling's intent — visible at the next fire through the opening inventory, ⛔ not a full re-read every fire — and under the repo's own finding(check-governed-merges): the merge queue's build-to-land skew can put a governed landing BELOW a round's --since boundary, where it is silently absent from the audit list #12633 ruling on landing order. The helper is not named because the criterion asks no windowed question.本席章程forLANE.md. The corpus already calls a lane file 席位章程 (lanes/director.md:1,lanes/triage.md:1, SKILL.md :770); the path spelling did not fit the width pin.Claude-Session:+Co-authored-by: Claude), per AGENTS.md's commit-message clause and the dev-agent file's own trailer rule; the harness attribution text named a model, and the repo rule won.Acceptance notes
check-governed-merges.mjsheader, finding(check-governed-merges): the merge queue's build-to-land skew can put a governed landing BELOW a round's --since boundary, where it is silently absent from the audit list #12633) — not a new defect; today's skew (19.6–34.1 min on 29/29 landings) is wider than the 874 s that header last measured but inside its 3,600 s budget. 承接者:this PR's :89 for the round-open reading; the sweep script for its own window.git log -1 -- PATHfor a path untouched above the floor answers the boundary commit's sha, not nothing — a reading, not a defect, and on the safe side for this criterion; anyone building a-1reading that must distinguish "never touched" from "touched below the floor" needsgit rev-list --max-parents=0beside it. 承接者:无.references/lanes/*.mdmirrors the round-open sequence (grep -rn 开轮 references/lanes/→ 0), so no lane file owes the twin. 承接者:无.references/seat-post-protocol.md:49 and digested at core-rules :79, no longer restated in SKILL.md's seat-post block — consistent with core-rules :3 (the corpus is SKILL.md plus its references). 承接者:无.维护者速读(草稿)
改了什么
.claude/skills/pm-dispatch/SKILL.md〈入口与角色〉开轮序列:原来的开轮标记一行改成两行(总行数不变 812/812,每行 ≤120 字节)。新增的判据:互斥清了以后,先 fetch,再读本文、core-rules 与本席章程三个文件在origin/main上的最新触碰提交;与本席上一条开轮标记记的不同,就先重读这三个文件再动手;开轮标记从此多带两样——这次读到的触碰提交,以及是否重读过。为付这一行,座位贴段里「归档自己派出的会话是不可移交的义务」一句离开 SKILL.md(它在references/seat-post-protocol.md交接清单第 8 条原文在、core-rules 摘要也在)。references/core-rules.md:20 原地吸收摘要(有收班简报→有简报,先写→核章程再写,119 字节),行数不变 151/151。seat-post-protocol.md一字未动——实测它没有开轮标记那一行,标记的内容规格只住在 SKILL.md。为什么改
分诊席 9 月 4 日就座时读入的 SKILL.md 与章程,在 PR #16698 于 9 月 8 日改掉三句载体纪律之后依然是它行事的版本:每小时的 fire 只带任务指令,没有任何东西告诉一个已就座的会话「你读过的文本被治理合并改了」,于是同一上午写出十三次新文本明令禁止的预挂。这不是纪律松懈,是协议缺口——事后巡检(#16995 半 1)能发现残留,事前却没有任何读数。本 PR 让治理合并在下一次 fire 的开轮盘点里可见,而且只在真的动过时才重读,不把每小时巡逻变成每小时通读。卡面写的是按时间窗
git log --since;实测本仓合并队列落地的提交,committer 日期比真正落到 main 早 19.6–34.1 分钟(最近 29 个 PR 全部如此),按 fire 时刻开窗会漏掉「提交日期在上次 fire 之前、落地在之后」的合并——这正是check-governed-merges.mjs头部已经裁过的 #12633 反序陷阱。所以判据改读 tip 上三文件的最新触碰提交并与上一标记比对:没有时间窗,浅检出也只会多读一次、不会误判为干净。风险与代价(含回滚)
seat-post-protocol.md交接清单第 8 条与 core-rules :79 都在,SKILL.md :285/:287 指向该清单;若维护者要求 SKILL.md 座位贴段保留这一句,替代付账方式是把新判据压回一行(信息损失在新规则一侧,最短拼写 133 字节装不下),或走棘轮头部的维护者出口。git fetch origin main与一次git log -1,零配额;命中时多一次三文件重读,只在治理合并真的落地时发生。git revert即回到0da638cd的原文。席位意见
(留空)
你要做的
读一遍 SKILL.md :89–:90 与 core-rules :20 三行,确认「读最新触碰提交并与上一标记比对」替代卡面的时间窗写法符合你对这条判据的本意;认可归档义务那一句退回 references 清单与摘要;人工合并(受管面,席位不翻 ready、不入队、不挂 auto-merge)。
🤖 Generated with Claude Code
https://claude.ai/code/session_01MoTv7pn338AZ71owsp19gQ
Generated by Claude Code